import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import os
import json
import datetime
import socket
import threading
import requests
from urllib.parse import urlparse
import time
import subprocess
import ctypes
import sys

class MainApp:
    def __init__(self, root):
        self.root = root
        self.load_config()
        self.old_config = self.config.copy()  # 用于比较配置变化
        self.update_title()
        self.create_main_widgets()
        self.focus_first_scan()
        self.serial_number = 1
        self.current_scan_index = 0
        self.history_records = []
        self.tcp_threads = []  # 替换原有的tcp_clients列表
        self.keyboard_modes = [False] * len(self.config["scanners"])
        self.switch_buttons = []
        self.api_enabled = False
        self.status_label = tk.Label(self.root, text="", font=('Arial', 10))
        self.status_label.pack(anchor=tk.SE, padx=5, pady=5)
        self.last_scan_times = {}  # 记录条码最后扫码时间（防重码）

        # 移除原有的ping_scanners线程启动代码
        # threading.Thread(target=self.ping_scanners, daemon=True).start()

        if self.config["mode"] != "keyboard":
            self.start_tcp_connections()

    def get_mode_zh(self):
        mode_zh = {
            "server": "服务器模式",
            "client": "客户端模式",
            "keyboard": "本地模式"
        }
        return mode_zh.get(self.config["mode"], "未知模式")

    def load_config(self):
        config_file = "scan_config.json"
        default_config = {
            "mode": "client",
            "save_path": "D:/条码记录",
            "scanners": [],
            "need_result": True,
            "need_confirm": True,
            "judge_chars": {"1": "OK", "0": "NG"},
            "client_ip": self.get_local_ip(),
            "scan_receive_format": "10进制",
            "result_receive_format": "10进制",
            "force_english": False,
            "api_enabled": False,
            "api_url": "http://127.0.0.1:5000/api/reportApi/report",
            "line_code": "",
            "station_code": "",
            "anti_duplicate_enabled": False,  # 防重码开关
            "anti_duplicate_seconds": 3,       # 防重码秒数
            "anti_duplicate_same_code_only": True,  # 新增"同一个码防重"选项
            "disable_proxy": True
        }
        if os.path.exists(config_file):
            try:
                with open(config_file, "r", encoding="utf-8") as f:
                    self.config = json.load(f)
                for key in default_config:
                    if key not in self.config:
                        self.config[key] = default_config[key]
            except Exception as e:
                print(f"加载配置文件出错: {e}")
                self.config = default_config
        else:
            self.config = default_config
            self.save_config()

    def get_local_ip(self):
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect(("8.8.8.8", 80))
            local_ip = s.getsockname()[0]
            s.close()
            return local_ip
        except Exception:
            return "127.0.0.1"

    class TCPConnectionThread(threading.Thread):
        def __init__(self, main_app, scanner_index):
            super().__init__()
            self.main_app = main_app
            self.scanner_index = scanner_index
            self.scanner = main_app.config["scanners"][scanner_index]
            self.running = True
            self.connected = False
            self.sock = None

        def run(self):
            while self.running:
                if not self.connected:
                    try:
                        # 获取扫码枪配置
                        ip = self.scanner.get("ip")
                        port = self.scanner.get("port", 8888)
                        display_name = self.scanner.get("name", ip)
                        
                        # 创建新的socket连接
                        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                        self.sock.settimeout(3)
                        
                        # 设置TCP Keepalive参数
                        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
                        if os.name == 'nt':  # Windows系统
                            self.sock.ioctl(socket.SIO_KEEPALIVE_VALS, (1, 60000, 10000))
                        
                        # 尝试连接
                        self.sock.connect((ip, port))
                        self.connected = True
                        
                        # 更新状态标签 - 使用扫码组编号
                        if self.main_app.config["need_result"]:
                            scan_group = self.scanner_index // 2
                        else:
                            scan_group = self.scanner_index
                        self.main_app.root.after(0, lambda sg=scan_group: self.main_app.status_label.config(
                            text=f"扫码{sg + 1} 连接成功", fg="green"))
                        
                        # 启动接收数据循环
                        self.receive_data()
                        
                    except Exception as e:
                        # 根据need_result配置计算扫码组编号
                        if self.main_app.config["need_result"]:
                            scan_group = self.scanner_index // 2
                        else:
                            scan_group = self.scanner_index
                        self.main_app.root.after(0, lambda sg=scan_group: self.main_app.status_label.config(
                            text=f"扫码{sg + 1}断开，将重试", fg="red"))
                        time.sleep(2)  # 2秒后重试
                else:
                    time.sleep(1)

        def receive_data(self):
            while self.connected and self.running:
                try:
                    self.sock.settimeout(1)
                    data = self.sock.recv(1024)
                    if data:
                        # 判断扫码/判断接收格式
                        config = self.main_app.config
                        idx = self.scanner_index
                        if config.get("need_result", True):
                            if idx % 2 == 0:
                                # 扫码接收格式
                                if config.get("scan_receive_format", "10进制") == "16进制":
                                    barcode = data.hex()
                                else:
                                    barcode = data.decode(errors='ignore').strip()
                            else:
                                # 判断接收格式
                                if config.get("result_receive_format", "10进制") == "16进制":
                                    barcode = data.hex()
                                else:
                                    barcode = data.decode(errors='ignore').strip()
                        else:
                            # 无需判断结果时，全部按扫码接收格式
                            if config.get("scan_receive_format", "10进制") == "16进制":
                                barcode = data.hex()
                            else:
                                barcode = data.decode(errors='ignore').strip()
                        self.main_app.root.after(0, lambda b=barcode: 
                            self.main_app.handle_tcp_scan(b, self.scanner_index))
                    elif data == b'':  # 连接被关闭
                        self.connected = False
                        self.sock.close()
                except socket.timeout:
                    continue
                except Exception as e:
                    # 根据need_result配置计算扫码组编号
                    if self.main_app.config["need_result"]:
                        scan_group = self.scanner_index // 2
                    else:
                        scan_group = self.scanner_index
                    self.main_app.root.after(0, lambda sg=scan_group: self.main_app.status_label.config(
                        text=f"扫码{sg + 1}断开，将重试", fg="red"))
                    self.connected = False
                    self.sock.close()
                    break

        def stop(self):
            self.running = False
            self.connected = False
            if self.sock:
                try:
                    self.sock.close()
                except Exception:
                    pass

    class TCPServerThread(threading.Thread):
        def __init__(self, main_app, scanner_index):
            super().__init__()
            self.main_app = main_app
            self.scanner_index = scanner_index
            self.scanner_config = main_app.config["scanners"][scanner_index]
            self.port = self.scanner_config.get("port", 8888)
            self.ip = self.scanner_config.get("ip", "0.0.0.0")
            self.running = True
            self.client_socket = None
            self.server_socket = None

        def run(self):
            try:
                self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                self.server_socket.bind((self.ip, self.port))
                self.server_socket.listen(5)  # 允许更多连接队列
                self.update_status(f"服务器模式: 监听 {self.ip}:{self.port} 等待扫码枪连接...", "blue")

                while self.running:
                    try:
                        # 设置超时以便能检查 self.running 标志
                        self.server_socket.settimeout(1.0)
                        self.client_socket, client_address = self.server_socket.accept()
                        self.update_status(f"扫码枪 {client_address[0]}:{client_address[1]} 已连接到 {self.ip}:{self.port}", "green")
                        self.receive_data()
                    except socket.timeout:
                        continue
                    except OSError: # 通常在 self.stop() 中关闭套接字时发生
                        break
                    except Exception as e:
                        if self.running:
                            self.update_status(f"连接中断，重新等待...", "red")
            except Exception as e:
                if self.running:
                    self.update_status(f"无法在 {self.ip}:{self.port} 上启动服务器: {e}", "red")
            finally:
                if self.server_socket:
                    self.server_socket.close()

        def receive_data(self):
            try:
                while self.running and self.client_socket:
                    data = self.client_socket.recv(1024)
                    if data:
                        # 判断扫码/判断接收格式
                        config = self.main_app.config
                        idx = self.scanner_index
                        if config.get("need_result", True):
                            if idx % 2 == 0:
                                # 扫码接收格式
                                if config.get("scan_receive_format", "10进制") == "16进制":
                                    barcode = data.hex()
                                else:
                                    barcode = data.decode(errors='ignore').strip()
                            else:
                                # 判断接收格式
                                if config.get("result_receive_format", "10进制") == "16进制":
                                    barcode = data.hex()
                                else:
                                    barcode = data.decode(errors='ignore').strip()
                        else:
                            # 无需判断结果时，全部按扫码接收格式
                            if config.get("scan_receive_format", "10进制") == "16进制":
                                barcode = data.hex()
                            else:
                                barcode = data.decode(errors='ignore').strip()
                        self.main_app.root.after(0, lambda b=barcode: 
                            self.main_app.handle_tcp_scan(b, self.scanner_index))
                    else:
                        break # 客户端关闭连接
            except Exception as e:
                pass # 任何异常都会导致循环结束
            finally:
                if self.client_socket:
                    self.client_socket.close()
                if self.running:
                    self.update_status(f"扫码枪连接已断开，重新等待连接...", "red")

        def stop(self):
            self.running = False
            # 关闭套接字以解除阻塞
            if self.server_socket:
                try: self.server_socket.close()
                except Exception: pass
            if self.client_socket:
                try: self.client_socket.close()
                except Exception: pass
        
        def update_status(self, message, color):
            # 多个线程可能同时更新状态，这可能会导致信息覆盖。
            # 对于这个应用，暂时允许覆盖。
            self.main_app.root.after(0, lambda: self.main_app.status_label.config(text=message, fg=color))

    def save_config(self):
        config_file = "scan_config.json"
        with open(config_file, "w", encoding="utf-8") as f:
            json.dump(self.config, f, ensure_ascii=False, indent=2)

    def create_main_widgets(self):
        # 扫码输入区域
        self.scan_frame = tk.Frame(self.root)
        self.scan_frame.pack(pady=5)
        self.scan_entries = []
        self.result_entries = []
        self.switch_buttons = []

        # 动态计算扫码枪数量
        if self.config["need_result"]:
            num_scanners = len(self.config["scanners"]) // 2
        else:
            num_scanners = len(self.config["scanners"])

        for i in range(num_scanners):
            scan_label_text = f"扫码{i + 1}"
            row = i
            tk.Label(self.scan_frame, text=scan_label_text).grid(row=row, column=0)
            entry = tk.Entry(self.scan_frame, width=40)
            entry.grid(row=row, column=1)
            entry.bind("<Return>", lambda e, idx=i: self.handle_scan(idx))
            self.scan_entries.append(entry)

            switch_btn = tk.Button(self.scan_frame, text="键盘", command=lambda idx=i: self.switch_input_mode(idx))
            switch_btn.grid(row=row, column=2)
            self.switch_buttons.append(switch_btn)

            if self.config["need_result"]:
                result_label_text = f"判断{i + 1}"
                tk.Label(self.scan_frame, text=result_label_text).grid(row=row, column=3)
                result_entry = tk.Entry(self.scan_frame, width=10)
                result_entry.grid(row=row, column=4)
                if self.config["need_confirm"]:
                    result_entry.bind("<Return>", lambda e, idx=i: self.handle_result(idx))
                else:
                    result_entry.bind("<KeyRelease>", lambda e, idx=i: self.handle_result(idx))
                self.result_entries.append(result_entry)

        # 历史记录区域
        self.history_frame = tk.Frame(self.root)
        self.history_frame.pack(pady=5)
        tk.Label(self.history_frame, text="历史记录:").pack(side=tk.LEFT)
        self.history_text = tk.Text(self.history_frame, width=40, height=10)
        self.history_text.pack(side=tk.LEFT)
        
        # 添加右键菜单
        self.history_menu = tk.Menu(self.history_text, tearoff=0)
        self.history_menu.add_command(label="清除记录", command=self.clear_history)
        self.history_text.bind("<Button-3>", self.show_history_menu)
        
        self.history_scroll = tk.Scrollbar(self.history_frame, command=self.history_text.yview)
        self.history_scroll.pack(side=tk.RIGHT, fill=tk.Y)
        self.history_text.configure(yscrollcommand=self.history_scroll.set)

        # 保存路径区域
        self.path_frame = tk.Frame(self.root)
        self.path_frame.pack(pady=5)
        tk.Label(self.path_frame, text="保存路径:").pack(side=tk.LEFT)
        self.path_entry = tk.Entry(self.path_frame, width=30)
        self.path_entry.pack(side=tk.LEFT)
        self.path_entry.insert(0, self.config["save_path"])
        tk.Button(self.path_frame, text="选择路径", command=self.choose_save_path).pack(side=tk.LEFT, padx=5)
        tk.Button(self.path_frame, text="打开记录文件夹", command=self.open_save_path).pack(side=tk.LEFT)

        # 菜单栏
        menu_bar = tk.Menu(self.root)
        menu_bar.add_command(label="设置", command=lambda: SettingsApp(self.root, self))
        self.root.config(menu=menu_bar)

    def focus_first_scan(self):
        if self.scan_entries:
            self.scan_entries[0].focus_set()
            self.current_scan_index = 0
            if self.config.get("force_english", False):
                self.set_entry_english(self.scan_entries[0])

    def handle_scan(self, index):
        self.current_scan_index = index
        if self.config["need_result"]:
            self.result_entries[index].focus_set()
        else:
            scan_code = self.scan_entries[index].get().strip()
            judge_char1 = list(self.config["judge_chars"].keys())[0]
            result_text = self.config["judge_chars"].get(judge_char1)
            recorded = self.save_record(scan_code, result_text)
            self.focus_next_scan(index)

    def handle_result(self, index):
        result_char = self.result_entries[index].get().strip()
        scan_code = self.scan_entries[index].get().strip()
        now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        result_text = self.config["judge_chars"].get(result_char, "未知结果")

        if result_char in self.config["judge_chars"]:
            if self.config["need_confirm"]:
                recorded = self.save_record(scan_code, result_text)
                self.focus_next_scan(index)
            else:
                recorded = self.save_record(scan_code, result_text)
                self.focus_next_scan(index)

    def save_record(self, barcode, result):
        # 条码为空不记录也不上传
        if not barcode:
            return False
        # 防重码判断
        if self.config.get("anti_duplicate_enabled", False):
            now_time = time.time()
            interval = self.config.get("anti_duplicate_seconds", 3)
            if self.config.get("anti_duplicate_same_code_only", True):
                last_time = self.last_scan_times.get(barcode, 0)
                if now_time - last_time < interval:
                    self.update_result(f"防重码：{interval}秒内重复扫码，已忽略")
                    return False  # 命中防重码，直接返回
                self.last_scan_times[barcode] = now_time
            else:
                # 全局防重（所有码都防重，任意码间隔）
                if hasattr(self, '_last_any_scan_time'):
                    last_time = self._last_any_scan_time
                else:
                    last_time = 0
                if now_time - last_time < interval:
                    self.update_result(f"防重码：{interval}秒内全局防重，已忽略")
                    return False  # 命中防重码，直接返回
                self._last_any_scan_time = now_time
        
        # API上传（如果启用）
        if self.config.get("api_enabled", False):
            # 同步执行API请求，等待结果
            api_success = self.send_api_request_sync(barcode, result)
            if not api_success:
                # API失败，不记录任何数据
                self.reset_inputs()
                return False
        
        now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        record = f"{now} - 条码: {barcode}, 结果: {result}"
        self.history_records.append(record)
        if len(self.history_records) > 10:
            self.history_records.pop(0)
        self.history_text.delete("1.0", tk.END)
        for rec in self.history_records:
            self.history_text.insert(tk.END, rec + "\n")
        self.history_text.see(tk.END)
        self.save_to_csv(barcode, result)
        self.reset_inputs()
        return True

    # 显示右键菜单
    def show_history_menu(self, event):
        self.history_menu.post(event.x_root, event.y_root)

    # 清除历史记录
    def clear_history(self):
        self.history_records = []
        self.history_text.delete("1.0", tk.END)

    def auto_save(self, index):
        scan_code = self.scan_entries[index].get().strip()
        judge_char1 = list(self.config["judge_chars"].keys())[0]
        result_text = self.config["judge_chars"].get(judge_char1)
        self.save_record(scan_code, result_text)

    def focus_next_scan(self, current_index):
        next_index = current_index + 1
        if next_index < len(self.scan_entries):
            self.scan_entries[next_index].focus_set()
            self.current_scan_index = next_index
            if self.config.get("force_english", False):
                self.set_entry_english(self.scan_entries[next_index])
        else:
            self.scan_entries[0].focus_set()
            self.current_scan_index = 0
            if self.config.get("force_english", False):
                self.set_entry_english(self.scan_entries[0])

    def reset_inputs(self):
        for entry in self.scan_entries:
            entry.delete(0, tk.END)
        if self.config["need_result"]:
            for entry in self.result_entries:
                entry.delete(0, tk.END)

    def save_to_csv(self, barcode, result):
        save_base = self.path_entry.get()
        if not save_base:
            messagebox.showerror("错误", "请先设置保存路径")
            return

        now = datetime.datetime.now()
        year_dir = now.strftime("%Y")
        month_dir = now.strftime("%m")
        date_str = now.strftime("%y%m%d")
        save_path = os.path.join(save_base, year_dir, month_dir)
        os.makedirs(save_path, exist_ok=True)

        file_name = f"{date_str}.csv"
        file_path = os.path.join(save_path, file_name)

        is_new_file = not os.path.exists(file_path)
        with open(file_path, "a", encoding="utf-8-sig") as f:
            if is_new_file:
                headers = "序号,时间,条码"
                if self.config["need_result"]:
                    headers += ",判断结果"
                f.write(headers + "\n")
            row = f"{self.serial_number},{now.strftime('%Y-%m-%d %H:%M:%S')},{barcode}"
            if self.config["need_result"]:
                row += f",{result}"
            f.write(row + "\n")
            self.serial_number += 1

    def choose_save_path(self):
        path = filedialog.askdirectory()
        if path:
            self.path_entry.delete(0, tk.END)
            self.path_entry.insert(0, path)
            self.config["save_path"] = path
            self.save_config()

    def open_save_path(self):
        path = self.path_entry.get()
        if os.path.exists(path):
            os.startfile(path)
        else:
            messagebox.showerror("错误", "路径不存在")

    def start_tcp_connections(self):
        # 停止现有连接
        self.close_tcp_connections()
        
        mode = self.config.get("mode", "client")

        # 根据模式选择启动客户端或服务器线程
        for i, scanner in enumerate(self.config["scanners"]):
            if mode == "client":
                thread = self.TCPConnectionThread(self, i)
            elif mode == "server":
                thread = self.TCPServerThread(self, i)
            else: # keyboard or other modes
                continue
            
            thread.daemon = True
            thread.start()
            self.tcp_threads.append(thread)

    def close_tcp_connections(self):
        # 停止所有TCP连接线程
        for thread in self.tcp_threads:
            if thread.is_alive():
                thread.stop()
        self.tcp_threads = []

    def handle_tcp_scan(self, barcode, index):
        if self.config["force_english"]:
            barcode = barcode.encode('ascii', 'ignore').decode('ascii')
            # 切换输入法到英文
            scan_group = index // 2 if self.config["need_result"] else index
            if scan_group < len(self.scan_entries):
                self.set_entry_english(self.scan_entries[scan_group])
            
        # 根据need_result计算对应的扫码组索引
        if self.config["need_result"]:
            scan_group = index // 2  # 每2个扫码枪为一组
        else:
            scan_group = index  # 无需判断结果时，索引直接对应扫码枪

        if scan_group < len(self.scan_entries):
            if self.keyboard_modes[scan_group]:
                return
                
            if self.config["need_result"]:
                # 偶数索引为扫码枪，奇数索引为判断结果
                if index % 2 == 0:
                    self.scan_entries[scan_group].delete(0, tk.END)
                    self.scan_entries[scan_group].insert(0, barcode)
                    if self.config["need_result"]:
                        self.result_entries[scan_group].focus_set()
                    else:
                        judge_char1 = list(self.config["judge_chars"].keys())[0]
                        result_text = self.config["judge_chars"].get(judge_char1)
                        scan_code = barcode
                        recorded = self.save_record(scan_code, result_text)
                        self.focus_next_scan(scan_group)
                else:
                    self.result_entries[scan_group].delete(0, tk.END)
                    self.result_entries[scan_group].insert(0, barcode)
                    result_char = barcode
                    result_text = self.config["judge_chars"].get(result_char, "未知结果")
                    if result_char in self.config["judge_chars"]:
                        if self.config["need_confirm"]:
                            scan_code = self.scan_entries[scan_group].get().strip()
                            recorded = self.save_record(scan_code, result_text)
                            self.focus_next_scan(scan_group)
                        else:
                            scan_code = self.scan_entries[scan_group].get().strip()
                            recorded = self.save_record(scan_code, result_text)
                            self.focus_next_scan(scan_group)
            else:
                # 无需判断结果时，直接处理扫码
                self.scan_entries[scan_group].delete(0, tk.END)
                self.scan_entries[scan_group].insert(0, barcode)
                judge_char1 = list(self.config["judge_chars"].keys())[0]
                result_text = self.config["judge_chars"].get(judge_char1)
                recorded = self.save_record(barcode, result_text)
                self.focus_next_scan(scan_group)

    def switch_input_mode(self, index):
        self.keyboard_modes[index] = not self.keyboard_modes[index]
        new_text = "TCP" if self.keyboard_modes[index] else "键盘"
        self.switch_buttons[index].config(text=new_text)

    def send_api_request_sync(self, barcode, result):
        """同步发送API请求，返回是否成功"""
        url = self.config["api_url"]
        line_code = self.config["line_code"]
        station_code = self.config["station_code"]

        if not self.validate_url(url):
            self.update_result("错误: 无效的API URL格式")
            return False

        if not line_code or not station_code:
            self.update_result("错误: 请先配置产线编号和工位编号")
            return False

        data = {
            "lineCode": line_code,
            "stationCode": station_code,
            "sn": barcode,
            "qty": 1,
            "result": result
        }

        proxies = None
        if self.config.get("disable_proxy", True):
            proxies = {'http': None, 'https': None}

        try:
            # 明确禁用代理，避免代理连接问题
            response = requests.post(url, json=data, timeout=10, proxies=proxies)
            return self.handle_api_response(response, barcode)
        except requests.exceptions.Timeout:
            self.update_result(f"⏰ API请求超时，SN: {barcode}")
            return False
        except requests.exceptions.ConnectionError:
            self.update_result(f"🔌 API连接错误，无法连接到服务器，SN: {barcode}")
            return False
        except Exception as e:
            self.update_result(f"💥 API未知错误: {str(e)}，SN: {barcode}")
            return False

    def handle_api_response(self, response, sn):
        try:
            if response.status_code == 200:
                # 尝试解析JSON响应
                try:
                    resp_json = response.json()
                    success = resp_json.get("success", None)  # 获取success字段，可能为None
                    
                    # 只有当success明确为false时才显示业务错误
                    if success is False:
                        err_message = resp_json.get("errMessage", "")
                        self.update_result(f"❌ {err_message}")
                        return False
                    
                    # 其他情况按正常流程处理
                    msg = resp_json.get("msg", "").strip().lower()
                    if msg in ["success", "ok", ""] or success is True:
                        self.update_result(f"✅ API请求成功，SN: {sn}")
                        return True
                    else:
                        self.update_result(f"❌ API请求失败，状态码: {response.status_code}，SN: {sn}")
                        
                except Exception:
                    # 非JSON响应，按原逻辑处理
                    self.update_result(f"✅ API请求成功，SN: {sn}")
                    return True
            else:
                self.update_result(f"❌ API请求失败，状态码: {response.status_code}，SN: {sn}")
                return False
        except Exception as e:
            self.update_result(f"响应处理错误，SN: {sn}: {str(e)}")
            return False

    def validate_url(self, url):
        try:
            result = urlparse(url)
            return all([result.scheme in ('http', 'https'), result.netloc])
        except:
            return False

    def update_result(self, message):
        self.root.after(0, lambda: self._update_result(message))

    def _update_result(self, message):
        self.history_text.insert(tk.END, f"\n{message}")
        self.history_text.see(tk.END)

    def update_title(self):
        line_code = self.config["line_code"]
        station_code = self.config["station_code"]
        mode_zh = self.get_mode_zh()
        title = f"{line_code}-{station_code} 扫码系统 - {mode_zh}"
        self.root.title(title)

    def apply_settings(self):
        """应用新设置，无需重启"""
        self.update_title()
        self.status_label.config(text="应用新设置中...", fg="blue")
        
        # 如果模式或扫码枪配置有变化，重启TCP连接
        need_restart_tcp = (
            self.config["mode"] != self.old_config["mode"] or
            self.config["scanners"] != self.old_config["scanners"]
        )
        
        if need_restart_tcp and self.config["mode"] != "keyboard":
            self.start_tcp_connections()  # 使用新的连接方法
        
        # 刷新界面组件
        self.refresh_widgets()
        
        # 保存当前配置作为旧配置，用于下次比较
        self.old_config = self.config.copy()
        
        # 显示成功提示并在2秒后清除
        self.status_label.config(text="设置已应用", fg="green")
        self.root.after(2000, lambda: self.status_label.config(text=""))

    def refresh_widgets(self):
        """动态刷新扫码输入框和结果输入框"""
        # 销毁现有输入框
        for widget in self.scan_frame.winfo_children():
            widget.destroy()
            
        # 清空现有列表
        self.scan_entries = []
        self.result_entries = []
        self.switch_buttons = []
        self.keyboard_modes = [False] * len(self.config["scanners"])
        
        # 重新计算扫码枪数量
        if self.config["need_result"]:
            num_scanners = len(self.config["scanners"]) // 2
        else:
            num_scanners = len(self.config["scanners"])

        for i in range(num_scanners):
            scan_label_text = f"扫码{i + 1}"
            row = i
            tk.Label(self.scan_frame, text=scan_label_text).grid(row=row, column=0)
            entry = tk.Entry(self.scan_frame, width=40)
            entry.grid(row=row, column=1)
            entry.bind("<Return>", lambda e, idx=i: self.handle_scan(idx))
            self.scan_entries.append(entry)

            switch_btn = tk.Button(self.scan_frame, text="键盘", command=lambda idx=i: self.switch_input_mode(idx))
            switch_btn.grid(row=row, column=2)
            self.switch_buttons.append(switch_btn)

            if self.config["need_result"]:
                result_label_text = f"判断{i + 1}"
                tk.Label(self.scan_frame, text=result_label_text).grid(row=row, column=3)
                result_entry = tk.Entry(self.scan_frame, width=10)
                result_entry.grid(row=row, column=4)
                if self.config["need_confirm"]:
                    result_entry.bind("<Return>", lambda e, idx=i: self.handle_result(idx))
                else:
                    result_entry.bind("<KeyRelease>", lambda e, idx=i: self.handle_result(idx))
                self.result_entries.append(result_entry)
                
        # 重新聚焦第一个输入框
        self.focus_first_scan()

    def set_entry_english(self, entry):
        if sys.platform.startswith('win'):
            try:
                hwnd = entry.winfo_id()
                hIMC = ctypes.windll.imm32.ImmGetContext(hwnd)
                ctypes.windll.imm32.ImmSetConversionStatus(hIMC, 0x0000, 0x0000)
                ctypes.windll.imm32.ImmReleaseContext(hwnd, hIMC)
            except Exception:
                pass


class SettingsApp:
    def __init__(self, root, main_app):
        self.root = root
        self.main_app = main_app
        self.settings_window = tk.Toplevel(root)
        self.settings_window.title("系统设置")
        self.entry_order = []
        self.settings_window.geometry("600x500")

        main_frame = tk.Frame(self.settings_window)
        main_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

        self.scroll_frame = tk.Frame(main_frame)
        self.scroll_frame.pack(fill=tk.BOTH, expand=True)

        self.canvas = tk.Canvas(self.scroll_frame)
        self.scrollbar = ttk.Scrollbar(self.scroll_frame, orient="vertical", command=self.canvas.yview)
        self.scrollable_frame = ttk.Frame(self.canvas)

        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: self.canvas.configure(
                scrollregion=self.canvas.bbox("all")
            )
        )

        self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        self.canvas.configure(yscrollcommand=self.scrollbar.set)

        self.canvas.pack(side="left", fill="both", expand=True)
        self.scrollbar.pack(side="right", fill="y")

        self.create_settings_content()

    def create_settings_content(self):
        self.button_frame = tk.Frame(self.scrollable_frame)

        tk.Label(self.scrollable_frame, text="运行模式:").grid(row=0, column=0, sticky='w')
        mode_frame = tk.Frame(self.scrollable_frame)
        mode_frame.grid(row=0, column=1, columnspan=3, sticky='ew')

        self.scrollable_frame.grid_columnconfigure(1, weight=1)
        self.scrollable_frame.grid_columnconfigure(2, weight=1)
        self.scrollable_frame.grid_columnconfigure(3, weight=1)

        self.mode_var = tk.StringVar(master=self.settings_window)
        self.mode_var.set(self.main_app.config["mode"])

        tk.Radiobutton(mode_frame, text="服务器模式", variable=self.mode_var, value="server").pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5)
        tk.Radiobutton(mode_frame, text="客户端模式", variable=self.mode_var, value="client").pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5)
        tk.Radiobutton(mode_frame, text="本地模式", variable=self.mode_var, value="keyboard").pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5)

        self.mode_var.trace_add("write", self.update_scanner_ui_for_mode)

        # 为服务器模式添加本机IP显示
        self.server_ip_frame = tk.Frame(self.scrollable_frame)
        self.server_ip_frame.grid(row=1, column=0, columnspan=4, sticky='w', pady=2)
        tk.Label(self.server_ip_frame, text="本机IP (供扫码枪连接):").pack(side=tk.LEFT)
        self.server_ip_value = tk.Label(self.server_ip_frame, text=self.main_app.get_local_ip(), fg="blue", font=('Arial', 10, 'bold'))
        self.server_ip_value.pack(side=tk.LEFT, padx=5)

        self.ip_labels = []
        self.name_labels = []

        tk.Label(self.scrollable_frame, text="API设置:").grid(row=2, column=0, pady=10)

        tk.Label(self.scrollable_frame, text="请求URL:").grid(row=3, column=0)
        self.api_url_entry = tk.Entry(self.scrollable_frame, width=40)
        self.api_url_entry.insert(0, self.main_app.config["api_url"])
        self.api_url_entry.bind("<Return>", lambda e, entry=self.api_url_entry: self.focus_next(entry))
        self.entry_order.append(self.api_url_entry)
        self.api_url_entry.grid(row=3, column=1)

        tk.Label(self.scrollable_frame, text="产线编号:").grid(row=4, column=0)
        self.line_code_entry = tk.Entry(self.scrollable_frame, width=20)
        self.line_code_entry.insert(0, self.main_app.config["line_code"])
        self.line_code_entry.bind("<Return>", lambda e, entry=self.line_code_entry: self.focus_next(entry))
        self.entry_order.append(self.line_code_entry)
        self.line_code_entry.grid(row=4, column=1)

        tk.Label(self.scrollable_frame, text="工位编号:").grid(row=5, column=0)
        self.station_code_entry = tk.Entry(self.scrollable_frame, width=20)
        self.station_code_entry.insert(0, self.main_app.config["station_code"])
        self.station_code_entry.bind("<Return>", lambda e, entry=self.station_code_entry: self.focus_next(entry))
        self.entry_order.append(self.station_code_entry)
        self.station_code_entry.grid(row=5, column=1)

        self.api_enabled_var = tk.BooleanVar(master=self.settings_window)
        self.api_enabled_var.set(self.main_app.config["api_enabled"])
        tk.Checkbutton(self.scrollable_frame, text="启用API发送", variable=self.api_enabled_var).grid(row=6, column=0)

        self.disable_proxy_var = tk.BooleanVar(master=self.settings_window)
        self.disable_proxy_var.set(self.main_app.config.get("disable_proxy", True))
        tk.Checkbutton(self.scrollable_frame, text="禁用代理", variable=self.disable_proxy_var).grid(row=6, column=1)

        self.need_result_var = tk.BooleanVar(master=self.settings_window)
        self.need_result_var.set(self.main_app.config["need_result"])
        self.need_result_checkbox = tk.Checkbutton(self.scrollable_frame, text="需要判断结果", variable=self.need_result_var,
                                                   command=self.update_need_result)
        self.need_result_checkbox.grid(row=7, column=0)

        self.need_confirm_var = tk.BooleanVar(master=self.settings_window)
        self.need_confirm_var.set(self.main_app.config["need_confirm"])
        tk.Checkbutton(self.scrollable_frame, text="需要判断确认", variable=self.need_confirm_var).grid(row=7, column=1)

        tk.Label(self.scrollable_frame, text="判断字符1:").grid(row=8, column=0)
        self.judge_char1 = tk.Entry(self.scrollable_frame, width=10)
        self.judge_char1.insert(0, list(self.main_app.config["judge_chars"].keys())[0])
        self.judge_char1.bind("<Return>", lambda e, entry=self.judge_char1: self.focus_next(entry))
        self.entry_order.append(self.judge_char1)
        self.judge_char1.grid(row=8, column=1)

        tk.Label(self.scrollable_frame, text="判断结果1:").grid(row=8, column=2)
        self.judge_result1 = tk.Entry(self.scrollable_frame, width=10)
        self.judge_result1.insert(0, list(self.main_app.config["judge_chars"].values())[0])
        self.judge_result1.bind("<Return>", lambda e, entry=self.judge_result1: self.focus_next(entry))
        self.entry_order.append(self.judge_result1)
        self.judge_result1.grid(row=8, column=3)

        tk.Label(self.scrollable_frame, text="判断字符2:").grid(row=9, column=0)
        self.judge_char2 = tk.Entry(self.scrollable_frame, width=10)
        self.judge_char2.insert(0, list(self.main_app.config["judge_chars"].keys())[1])
        self.judge_char2.bind("<Return>", lambda e, entry=self.judge_char2: self.focus_next(entry))
        self.entry_order.append(self.judge_char2)
        self.judge_char2.grid(row=9, column=1)

        tk.Label(self.scrollable_frame, text="判断结果2:").grid(row=9, column=2)
        self.judge_result2 = tk.Entry(self.scrollable_frame, width=10)
        self.judge_result2.insert(0, list(self.main_app.config["judge_chars"].values())[1])
        self.judge_result2.bind("<Return>", lambda e, entry=self.judge_result2: self.focus_next(entry))
        self.entry_order.append(self.judge_result2)
        self.judge_result2.grid(row=9, column=3)

        tk.Label(self.scrollable_frame, text="扫码接收格式:").grid(row=10, column=0)
        self.scan_receive_format_var = tk.StringVar(master=self.settings_window)
        self.scan_receive_format_var.set(self.main_app.config["scan_receive_format"])
        tk.Radiobutton(self.scrollable_frame, text="10进制", variable=self.scan_receive_format_var, value="10进制").grid(row=10, column=1)
        tk.Radiobutton(self.scrollable_frame, text="16进制", variable=self.scan_receive_format_var, value="16进制").grid(row=10, column=2)

        tk.Label(self.scrollable_frame, text="判断接收格式:").grid(row=11, column=0)
        self.result_receive_format_var = tk.StringVar(master=self.settings_window)
        self.result_receive_format_var.set(self.main_app.config["result_receive_format"])
        tk.Radiobutton(self.scrollable_frame, text="10进制", variable=self.result_receive_format_var, value="10进制").grid(row=11, column=1)
        tk.Radiobutton(self.scrollable_frame, text="16进制", variable=self.result_receive_format_var, value="16进制").grid(row=11, column=2)

        self.force_english_var = tk.BooleanVar(master=self.settings_window)
        self.force_english_var.set(self.main_app.config["force_english"])
        tk.Checkbutton(self.scrollable_frame, text="强制英文状态", variable=self.force_english_var).grid(row=12, column=0)

        # 防重码设置
        self.anti_duplicate_var = tk.BooleanVar(master=self.settings_window)
        self.anti_duplicate_var.set(self.main_app.config.get("anti_duplicate_enabled", False))
        tk.Checkbutton(self.scrollable_frame, text="启用防重码", variable=self.anti_duplicate_var).grid(row=13, column=0)
        tk.Label(self.scrollable_frame, text="防重码秒数:").grid(row=13, column=1)
        self.anti_duplicate_seconds_entry = tk.Entry(self.scrollable_frame, width=5)
        self.anti_duplicate_seconds_entry.insert(0, str(self.main_app.config.get("anti_duplicate_seconds", 3)))
        self.anti_duplicate_seconds_entry.grid(row=13, column=2)
        # 新增"同一个码防重"选项
        self.anti_duplicate_same_code_only_var = tk.BooleanVar(master=self.settings_window)
        self.anti_duplicate_same_code_only_var.set(self.main_app.config.get("anti_duplicate_same_code_only", True))
        tk.Checkbutton(self.scrollable_frame, text="同一个码防重", variable=self.anti_duplicate_same_code_only_var).grid(row=13, column=3)

        self.scanner_frame = tk.Frame(self.scrollable_frame)
        self.scanner_frame.grid(row=14, column=0, columnspan=4, pady=10)
        self.scanner_entries = []
        for i, scanner in enumerate(self.main_app.config["scanners"]):
            self.add_scanner_row(i, scanner)

        self.update_button_position()
        self.update_scanner_ui_for_mode()

    def update_scanner_ui_for_mode(self, *args):
        """根据运行模式更新整个扫描仪UI"""
        mode = self.mode_var.get()

        # 显示/隐藏服务器IP信息
        if mode == 'server':
            self.server_ip_frame.grid()
        else:
            self.server_ip_frame.grid_remove()

        # 清空并重建扫描仪条目以反映模式变化
        for widget in self.scanner_frame.winfo_children():
            widget.destroy()
        
        self.scanner_entries.clear()
        self.entry_order = [e for e in self.entry_order if not isinstance(e, tk.Entry)]
        self.ip_labels.clear()
        self.name_labels.clear()

        scanners = self.main_app.config.get("scanners", [])
        for i, scanner_config in enumerate(scanners):
            self.add_scanner_row(i, scanner_config)
        
        self.update_button_position()

    def add_scanner_row(self, index, scanner):
        mode = self.mode_var.get()
        var = tk.IntVar(value=1)
        
        # 根据need_result确定显示文本
        if self.need_result_var.get():
            group_num = (index // 2) + 1
            if index % 2 == 0:
                checkbutton_text = f"扫码枪{group_num}"
            else:
                checkbutton_text = f"扫码枪{group_num}.1"
        else:
            checkbutton_text = f"扫码枪{index + 1}"
            
        checkbutton = tk.Checkbutton(self.scanner_frame, variable=var, text=checkbutton_text)
        checkbutton.grid(row=index, column=0, sticky='w', padx=5)

        entry_dict = {"var": var}
        col = 1

        # IP输入框 (用于客户端和服务器模式)
        if mode in ['client', 'server']:
            tk.Label(self.scanner_frame, text="IP地址:").grid(row=index, column=col)
            col += 1
            ip_entry = tk.Entry(self.scanner_frame, width=20)
            if mode == 'server':
                ip_entry.insert(0, scanner.get("ip", "0.0.0.0"))
            else:
                ip_entry.insert(0, scanner.get("ip", ""))
            ip_entry.grid(row=index, column=col)
            col += 1
            entry_dict["ip"] = ip_entry
            self.entry_order.append(ip_entry)

        # 名称输入框 (仅用于本地模式)
        if mode == 'keyboard':
            tk.Label(self.scanner_frame, text="名称:").grid(row=index, column=col)
            col += 1
            name_entry = tk.Entry(self.scanner_frame, width=20)
            name_entry.insert(0, scanner.get("name", f"扫码枪{index + 1}"))
            name_entry.grid(row=index, column=col)
            col += 1
            entry_dict["name"] = name_entry
            self.entry_order.append(name_entry)

        # 端口输入框 (用于客户端和服务器模式)
        if mode in ['client', 'server']:
            tk.Label(self.scanner_frame, text="端口号:").grid(row=index, column=col)
            col += 1
            port_entry = tk.Entry(self.scanner_frame, width=10)
            port_entry.insert(0, str(scanner.get("port", 8888)))
            port_entry.grid(row=index, column=col)
            col += 1
            entry_dict["port"] = port_entry
            self.entry_order.append(port_entry)
            
        self.scanner_entries.append(entry_dict)

    def remove_scanner(self):
        if self.scanner_entries:
            self.scanner_entries.pop()
            # 重建UI是处理小部件和列表的最简单、最安全的方法
            self.update_scanner_ui_for_mode()

    def update_button_position(self):
        if hasattr(self, 'button_frame'):
            self.button_frame.destroy()

        # 计算按钮位置，放在扫码枪配置之后
        row_position = len(self.scanner_entries) + 14
        self.button_frame = tk.Frame(self.scrollable_frame)
        self.button_frame.grid(row=row_position, column=0, columnspan=4, pady=10)

        self.add_btn = tk.Button(self.button_frame, text="添加扫码枪", command=self.add_scanner)
        self.remove_btn = tk.Button(self.button_frame, text="移除扫码枪", command=self.remove_scanner)
        self.save_btn = tk.Button(self.button_frame, text="保存设置", command=self.save_settings)

        self.add_btn.pack(side=tk.LEFT, padx=5)
        self.remove_btn.pack(side=tk.LEFT, padx=5)
        self.save_btn.pack(side=tk.LEFT, padx=5)

    def add_scanner(self):
        row = len(self.scanner_entries)
        self.add_scanner_row(row, {"ip": "", "port": 8888})
        self.update_button_position()

    def save_settings(self):
        # 更新主应用配置
        self.main_app.config["mode"] = self.mode_var.get()
        self.main_app.config["need_result"] = self.need_result_var.get()
        self.main_app.config["need_confirm"] = self.need_confirm_var.get()
        self.main_app.config["judge_chars"] = {
            self.judge_char1.get(): self.judge_result1.get(),
            self.judge_char2.get(): self.judge_result2.get()
        }
        self.main_app.config["scanners"] = []
        
        mode = self.mode_var.get()

        # 处理扫码枪配置
        for entry_dict in self.scanner_entries:
            if entry_dict["var"].get():  # 仅保存启用的扫码枪
                scanner_config = {}
                if mode == 'client':
                    ip = entry_dict["ip"].get().strip()
                    port_str = entry_dict["port"].get().strip()
                    if not self.validate_ip(ip):
                        messagebox.showerror("错误", f"无效的IP地址: {ip}")
                        return
                    scanner_config['ip'] = ip
                    try:
                        scanner_config['port'] = int(port_str) if port_str else 8888
                    except ValueError:
                        messagebox.showerror("错误", f"无效的端口号: {port_str}")
                        return

                elif mode == 'server':
                    ip = entry_dict["ip"].get().strip()
                    port_str = entry_dict["port"].get().strip()
                    if not self.validate_ip(ip):
                        messagebox.showerror("错误", f"无效的IP地址: {ip}")
                        return
                    scanner_config['ip'] = ip
                    try:
                        scanner_config['port'] = int(port_str) if port_str else 8888
                    except ValueError:
                        messagebox.showerror("错误", f"无效的端口号: {port_str}")
                        return

                elif mode == 'keyboard':
                    name = entry_dict["name"].get().strip()
                    scanner_config['name'] = name
                    scanner_config['ip'] = '127.0.0.1' 
                    scanner_config['port'] = 8888
                
                self.main_app.config["scanners"].append(scanner_config)

        self.main_app.config["scan_receive_format"] = self.scan_receive_format_var.get()
        self.main_app.config["result_receive_format"] = self.result_receive_format_var.get()
        self.main_app.config["force_english"] = self.force_english_var.get()
        self.main_app.config["api_enabled"] = self.api_enabled_var.get()
        self.main_app.config["disable_proxy"] = self.disable_proxy_var.get()
        self.main_app.config["api_url"] = self.api_url_entry.get().strip()
        self.main_app.config["line_code"] = self.line_code_entry.get().strip()
        self.main_app.config["station_code"] = self.station_code_entry.get().strip()
        # 防重码参数保存
        self.main_app.config["anti_duplicate_enabled"] = self.anti_duplicate_var.get()
        try:
            self.main_app.config["anti_duplicate_seconds"] = int(self.anti_duplicate_seconds_entry.get())
        except ValueError:
            self.main_app.config["anti_duplicate_seconds"] = 3
        # 保存"同一个码防重"选项
        self.main_app.config["anti_duplicate_same_code_only"] = self.anti_duplicate_same_code_only_var.get()

        # 保存配置并应用
        self.main_app.save_config()
        self.main_app.apply_settings()  # 立即应用设置
        self.settings_window.destroy()

    def focus_next(self, entry):
        current_index = self.entry_order.index(entry)
        next_index = (current_index + 1) % len(self.entry_order)
        self.entry_order[next_index].focus_set()

    def update_need_result(self):
        """当需要判断结果选项变化时，更新界面"""
        # 销毁现有扫码枪配置
        for widget in self.scanner_frame.winfo_children():
            widget.destroy()
        self.scanner_entries = []
        self.ip_labels = []
        
        # 重新创建扫码枪配置
        for i, scanner in enumerate(self.main_app.config["scanners"]):
            self.add_scanner_row(i, scanner)
            
        self.update_button_position()

    def validate_ip(self, ip):
        """验证IP地址格式，本地模式下直接返回True"""
        if self.mode_var.get() == "keyboard":
            return True
            
        try:
            socket.inet_aton(ip)
            return True
        except (socket.error, ValueError):
            return False

if __name__ == "__main__":
    root = tk.Tk()
    app = MainApp(root)
    root.mainloop()
